package org.sbbs.base.dao.hibernate;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.SessionFactory;
import org.sbbs.base.dao.BaseDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.orm.hibernate3.HibernateTemplate;

/**
 * This class serves as the Base class for all other DAOs - namely to hold common CRUD methods that they might all use.
 * You should only need to extend this class when your require custom CRUD logic.
 * <p/>
 * <p>
 * To register this class in your Spring context file, use the following XML.
 * 
 * <pre>
 *      &lt;bean id="fooDao" class="org.sbbs.dao.hibernate.GenericDaoHibernate"&gt;
 *          &lt;constructor-arg value="org.sbbs.model.Foo"/&gt;
 *      &lt;/bean&gt;
 * </pre>
 * 
 * @author <a href="mailto:bwnoll@gmail.com">Bryan Noll</a>
 * @param <T> a type variable
 * @param <PK> the primary key for that type
 */
public class BaseDaoHibernate<T, PK extends Serializable>
    implements BaseDao<T, PK> {
    /**
     * Log variable for all child classes. Uses LogFactory.getLog(getClass()) from Commons Logging
     */
    protected final Log log = LogFactory.getLog( getClass() );

    private Class<T> persistentClass;

    private HibernateTemplate hibernateTemplate;

    private SessionFactory sessionFactory;

    /**
     * Constructor that takes in a class to see which type of entity to persist. Use this constructor when subclassing.
     * 
     * @param persistentClass the class type you'd like to persist
     */
    public BaseDaoHibernate( final Class<T> persistentClass ) {
        this.persistentClass = persistentClass;
    }

    /**
     * Constructor that takes in a class and sessionFactory for easy creation of DAO.
     * 
     * @param persistentClass the class type you'd like to persist
     * @param sessionFactory the pre-configured Hibernate SessionFactory
     */
    public BaseDaoHibernate( final Class<T> persistentClass, SessionFactory sessionFactory ) {
        this.persistentClass = persistentClass;
        this.sessionFactory = sessionFactory;
        this.hibernateTemplate = new HibernateTemplate( sessionFactory );
    }

    public HibernateTemplate getHibernateTemplate() {
        return this.hibernateTemplate;
    }

    public SessionFactory getSessionFactory() {
        return this.sessionFactory;
    }

    @Autowired
    @Required
    public void setSessionFactory( SessionFactory sessionFactory ) {
        this.sessionFactory = sessionFactory;
        this.hibernateTemplate = new HibernateTemplate( sessionFactory );
    }

    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    public void setPersistentClass( Class<T> persistentClass ) {
        this.persistentClass = persistentClass;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings( "unchecked" )
    public List<T> getAll() {
        return hibernateTemplate.loadAll( this.persistentClass );
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings( "unchecked" )
    public List<T> getAllDistinct() {
        Collection result = new LinkedHashSet( getAll() );
        return new ArrayList( result );
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings( "unchecked" )
    public T get( PK id ) {
        T entity = (T) hibernateTemplate.get( this.persistentClass, id );

        if ( entity == null ) {
            log.warn( "Uh oh, '" + this.persistentClass + "' object with id '" + id + "' not found..." );
            throw new ObjectRetrievalFailureException( this.persistentClass, id );
        }

        return entity;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings( "unchecked" )
    public boolean exists( PK id ) {
        T entity = (T) hibernateTemplate.get( this.persistentClass, id );
        return entity != null;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings( "unchecked" )
    public T save( T object ) {

        return (T) hibernateTemplate.merge( object );
    }

    /**
     * {@inheritDoc}
     */
    public void remove( PK id ) {
        hibernateTemplate.delete( this.get( id ) );
    }

}
